package games.mapacman.client;

import games.mapacman.common.ZoneChangePoint;
import games.mapacman.common.common;
import games.mapacman.common.consts;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import marauroa.client.ClientFramework;
import marauroa.client.LoginFailedException;
import marauroa.client.TimeoutException;
import marauroa.client.net.IPerceptionListener;
import marauroa.client.net.PerceptionHandler;
import marauroa.common.Log4J;
import marauroa.common.game.RPAction;
import marauroa.common.game.RPObject;
import marauroa.common.net.message.MessageS2CPerception;
import marauroa.common.net.message.TransferContent;
import marauroa.server.game.container.PlayerEntryContainer;
import marauroa.server.game.rp.RPWorld;


public class MaPacman extends ClientFramework implements Runnable
  {
/** the logger instance. */
	private static final marauroa.common.Logger logger = Log4J.getLogger(MaPacman.class);

	private PerceptionHandler phandler;
	private RPWorld world;
	private Map<RPObject.ID,RPObject> world_objects;
	private Map<RPObject.ID,RPObject> wall_objects;
	private Vector<RPObject> players;
	private Vector<RPObject> ghosts;
	private Vector<RPObject> dots;
	private MaPacmanGui GUI;
	private boolean running;
	private String myPlayerName;
	private Vector<String> ChatMessages;
	
	private GameScreen screen;
	private String user;
	private String pass;
	private String server;
	private int port;
	
	private final int WANTED_FPS=15;
	protected String currentZone;
	
	void sendChatMsg(String text)
	{
    	RPAction action= new RPAction();
	    action.put("type",consts.CHATMESSAGE);
	    action.put("content",text);
		this.send(action);
	}
	
	void changeDirection(int dir)
	{
    	RPAction action= new RPAction();
	    action.put("type",consts.CHANGEDIR);
	    action.put("content",dir);
		this.send(action);
	}
	
	public void initialize()
	{
		players = new Vector<RPObject>();
		ghosts = new Vector<RPObject>();
		dots = new Vector<RPObject>();
		myPlayerName = null;
		running=true;
		phandler = new PerceptionHandler(new MaPacmanPerceptionListener());
		world_objects=new HashMap<RPObject.ID, RPObject>();
		wall_objects=new HashMap<RPObject.ID, RPObject>();
		common.initRPClasses();
		GUI = new MaPacmanGui(this);
		GUI.setVisible(true);
		GUI.init();
	}
	
	public MaPacman(boolean logging, String username, String password, String Server, int port) {
		
		
		initialize();		
		setGameOptions(username,password,Server,port);
		(new Thread(this)).start();
	}
	
	public void startGame(String username, String password, String Server, int port) {
		try 
		{
			GUI.printText("尝试登录到 "+Server+":"+port);
			this.connect(Server,port);
			this.login(username,password);
				
				
				screen=GUI.prepareScreen();
				gameLoop();
			}
		catch (LoginFailedException e)
		{
			GUI.ErrorMsg("您指定了错误的密码或用户名，或者使用了错误的版本");
		}
		catch (TimeoutException e) {
			GUI.ErrorMsg("连接服务器超时\n没有互联网连接或服务器已关闭 ??");

		}
		catch (Exception e) {
			GUI.ErrorMsg("似乎登录失败了 :(");
			e.printStackTrace();
		}
	
	}

	public MaPacman(boolean logging)
	{	
		
		initialize();
	}

	public void gameLoop()
	{
		long fpstimer= System.currentTimeMillis();
		long fpscounter=0;
		screen.setPlayerGhostVector(players,ghosts);
		
		// time of 1 turn in ms. Should be 400 but needs to be adjusted for slow PCs
		int estTurntime =400;
		
		double sleeptime=0;
		while(running)
		{   
			fpscounter++;
			long currentTime = System.currentTimeMillis();	
								
			screen.nextFrame();

			this.loop(0);

			
			// in 1 turntime 5fps should be made ...
			sleeptime = 1000/((1000/estTurntime)*5)-(System.currentTimeMillis()-currentTime);
			
			if (sleeptime<1.0)
				sleeptime=1;
			try 
			{
				Thread.sleep((int)sleeptime);
			} 
			catch (InterruptedException e) 
			{
				e.printStackTrace();
			}
			if ((System.currentTimeMillis()-fpstimer)>1000)
			{
				fpstimer=System.currentTimeMillis();
				System.out.println("FPS:"+fpscounter);
				if ( GUI.getScreen().noScrollinLastTurn())
				{
					if (fpscounter>17)estTurntime+=35;
					if (fpscounter<13 && estTurntime>36)estTurntime-=35;
				}

				fpscounter=0;
			}
		}		
	
	}

  class MaPacmanPerceptionListener implements IPerceptionListener
    {
    public boolean onMyRPObject(RPObject added,RPObject deleted)
      {        
      return true;
      }
	
    public boolean onAdded(RPObject object)
    {
    try
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onAdded, Object("+object.getID()+") added");
	 
			if (object.get("type").equals(consts.TYPE_GHOST))
			{
				object.put("move",0);
				object.put("imgCounter",0);
				ghosts.add(object);
			}
			else if (object.get("type").equals(consts.TYPE_DOT) 
					|| object.get("type").equals(consts.TYPE_SUPERDOT) 
					|| object.get("type").equals(consts.TYPE_FRUIT) 
					|| object.get("type").equals(consts.TYPE_POWERPILL) )
			{
				screen.addDot(object);
			}
			else if (object.get("type").equals(consts.TYPE_PLAYER))
			{
				object.put("move",0);
				object.put("imgCounter",0);
				players.add(object);
				if (object.get("name").equals(myPlayerName))
					screen.setPlayer(object);
				if (object.has("text") && object.get("text").length()>0)
					GUI.printText(object.get("name")+" : " +object.get("text"));
			} 
			else if (object.get("type").equals(consts.TYPE_WALL))
			{
				if (!wall_objects.containsValue(object))
				{
					wall_objects.put(object.getID(),object);
					screen.addWall(object.getInt("x"),object.getInt("y"));
					screen.drawWall(object);
				}
			}
			else if (object.get("type").equals(consts.TYPE_ZONECHANGE))
			{
				screen.addZoneChange(new ZoneChangePoint(object));
			}
			else if (object.get("type").equals(consts.TYPE_EATENSIGN))
			{
				screen.addEatenSign(object);
			}
      }
    catch(Exception e)
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onAdded" + e);
     }
    return false;
    }
    
  public boolean onModifiedAdded(RPObject object, RPObject changes)
    {
    // NOTE: We do handle the perception here ourselves. See that we return true 
    try
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onModifiedAdded, Object("+object.getID()+") modified in Game Objects container");
      
      object.applyDifferences(changes,null);
	  
      if (object.get("type").equals(consts.TYPE_GHOST))
		{
		  	object.put("move",0);
		}
		else if (object.get("type").equals(consts.TYPE_DOT) 
				|| object.get("type").equals(consts.TYPE_SUPERDOT)
				|| object.get("type").equals(consts.TYPE_FRUIT)
				|| object.get("type").equals(consts.TYPE_POWERPILL) )
		{
			screen.addDot(object);
		}
		else if (object.get("type").equals(consts.TYPE_PLAYER))
		{
			// if (object.getInt("power")>0) System.out.println("powermode");
			object.put("move",0);
			if (object.has("text") && object.get("text").length()>0)
				GUI.printText(object.get("name")+" : " +object.get("text"));
			/*if (!changes.get("zoneid").equals(object.get("zoneid")))
				players.remove(object);*/
		} 
		else if (object.get("type").equals(consts.TYPE_WALL))
		{

		}		
      
	  }
    catch(Exception e)
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onModifiedAdded" + changes.toString());
      logger.debug("MaPacman::MaPacmanPerceptionListener::onModifiedAdded" + e);
      }
    return true;
    }

  public boolean onModifiedDeleted(RPObject object, RPObject changes)
    {
    try
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onModifiedDeleted, Object("+object.getID()+") added to Static Objects container");
      
      object.applyDifferences(null,changes);
      }
    catch(Exception e)
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onModifiedDeleted" + e);
      }
    return true;
    }

  public boolean onDeleted(RPObject object)
    {
    try
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onDeleted"+"Object("+object.getID()+") removed from Static Objects container");
	  if (object.get("type").equals(consts.TYPE_PLAYER))
		{
		  for (RPObject player : players)
		  {
			  if (player.get("name").equals(object.get("name")))
			  { 
				  players.remove(player);
				  break;
			  }
		  }
		} 
	  else if (object.get("type").equals(consts.TYPE_EATENSIGN))
		{
			//screen.removeEatenSign(object);
		}
	  else if (object.get("type").equals(consts.TYPE_GHOST))
		{
		  	
		}
		else if (object.get("type").equals(consts.TYPE_DOT))
		{
			
		}
		else if (object.get("type").equals(consts.TYPE_WALL))
		{

		}
      }
    catch(Exception e)
      {
      logger.debug("MaPacman::MaPacmanPerceptionListener::onDeleted"+e);
      }
    return false;
    }
  
  public int onTimeout()
    {      
    logger.debug("MaPacman::MaPacmanPerceptionListener::onTimeout"+"Request resync");
    resync();
    return 0;
    }
  
  public void onSynced()
    {
    logger.debug("MaPacman::MaPacmanPerceptionListener::onSynced"+"Synced with server state.");
    }
  
  public void onUnsynced()
    {
    logger.debug("MaPacman::MaPacmanPerceptionListener::onUnsynced"+"Request resync");
    resync();
    }
 
  public void onException(Exception e, marauroa.common.net.message.MessageS2CPerception perception)      
    {
    logger.debug("MaPacman::MaPacmanPerceptionListener::onException"+perception.toString());
    logger.debug("MaPacman::MaPacmanPerceptionListener::onException"+e);
    System.exit(-1);
    
    }

@Override
public boolean onClear() {
	return false;
}

@Override
public void onPerceptionBegin(byte type, int timestamp) {
	
}

@Override
public void onPerceptionEnd(byte type, int timestamp) {
	
}
  }


	
	protected void onPerception(MessageS2CPerception message) {
		
		if(message.getPerceptionType()==1/*Perception.SYNC*/)
		{
			  /** Full object is normal object+hidden objects */
	        RPObject hidden=message.getMyRPObjectAdded(); //??
	        RPObject object=null;
	        
	        for(RPObject search: message.getAddedRPObjects())
	          {
	          if(search.getID().equals(hidden.getID()))
	            {
	            object=(RPObject)search.clone();
	            break;
	            }
	          }
			  
	        try {
				object.applyDifferences(hidden,null);
			} catch (Exception e) {
				e.printStackTrace();
			}
	        
			// Player has changed Zone
				  screen.clear();
				  players.clear();
				  ghosts.clear();
				  wall_objects.clear();
				  dots.clear();
		}

    try {
      phandler.apply(message,world_objects);
    } catch (Exception e) {
      e.printStackTrace();
    }
      
	}	


	protected List<TransferContent> onTransferREQ(List<TransferContent> items) {
		// TODO Auto-generated method stub
		return null;
	}

	protected void onTransfer(List<TransferContent> items) {
		// TODO Auto-generated method stub
		
	}

	protected void onAvailableCharacters(String[] characters)
    {
		
    try
      {
      if (chooseCharacter(characters[0]))
		  GUI.printText("logged in as "+characters[0]);
	  	  myPlayerName=characters[0].trim();
		  for (RPObject player : world_objects.values())
		  {
			  if (player.get("type").equals(consts.TYPE_PLAYER) && 
			      player.get("name").equals(getMyPlayerName()))
			  {
				  screen.setPlayer(player);
				  break;
			  }
		  }
		  screen.setPlayerName(myPlayerName);
      }
    catch(Exception e)
      {
      logger.error(e.getMessage());
      }
    }

	@Override
	protected void onServerInfo(String[] info) {

	}

	protected String getGameName() {
		return consts.GAMENAME;
	}

	protected String getVersionNumber() {
		return consts.VERSION;
	}
	
	public static void main (String[] args)
	{
    String[] allowed={/*"*"*/};
    

    String[] rejected={};
    

		if (args.length==4)
			new MaPacman(true,args[0],args[1], args[2], Integer.parseInt(args[3]) );
		else
			new MaPacman(true);
	}

	public void close() {
		GUI.setVisible(false);
		running = false;	
	}

	public String getMyPlayerName() {
		return myPlayerName;
	}

	public void run() {
		startGame(user,pass,server,port);
	}

	public void setGameOptions(String username, String password, String server, int port) {
		this.user=username;
		this.pass=password;
		this.server=server;
		this.port=port;
		
	}

	public void stopGame() {
		running=false;
		try 
		{
			Thread.sleep(300);
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		System.exit(1);
		
	}

	@Override
	protected void onPreviousLogins(List<String> arg0) {
		
	}
 
  }
